package AFMAnalysis;

import java.awt.Color;
import java.awt.Polygon;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;

import Voronoi.*;

import javax.imageio.ImageIO;

public class GrainDetector {

	Pixel[][] pixels;
	ArrayList<Blob> blobArray;
	ArrayList<Edge> edges;
	ArrayList<Line> lines;
	ArrayList<Grain> grains;
	Edger edger;
	ArrayList<ScaledBlob> scaledBlobs;
	ArrayList<Integer> idKey;
	ArrayList<Integer[]> centers;
	ArrayList<Integer[]> voronoiLines;
	ArrayList<VoronoiPolygon> polygons;
	List<GraphEdge> voronoiEdges;

	public GrainDetector(Pixel[][] pix, ArrayList<Blob> blobs){

		pixels = pix;
		blobArray = blobs;
		lines = new ArrayList<Line>();
		edger = new Edger(pixels);
		grains = new ArrayList<Grain>();
		centers = new ArrayList<Integer[]>();
		polygons = new ArrayList<VoronoiPolygon>();
	}

	/**
	 * This method uses the edger to create the edges in the image, and then
	 * translates the information from the edger to this class
	 * 
	 */

	public void getEdges(){

		edger.createEdges(blobArray);
		edges = edger.edges;
		scaledBlobs = edger.scaledBlobArray;
		idKey = edger.idKey;
	}

	/**
	 * This method calculates the average number of Pixels per edge in the image
	 * 
	 * @return The average number of Pixels per Edge in the image
	 */

	public int pixPerEdge(){

		int counter = 0;
		for(int count = 0; count < edges.size(); count++){
			for(int count1 = 0; count1 < edges.get(count).pixels.size(); count1++){
				counter++;
			}
		}
		return counter/edges.size();
	}

	/**
	 * This method creates the lines for each edge by defining three limiting parameters:
	 * the minimum size of a Line (5), the maximum size of a Line (determined by the
	 * average number of Pixels per Edge), and the tolerance for the r value on a Line.
	 * These Lines are added to each Edge's line ArrayList for further analysis.
	 * 
	 * @param tolerance The tolerance for the Line regression.
	 */

	public void createLines(double tolerance){

		int counter = 0;
		//The 3.5 is a variable parameter, it changes the maximum size of a line
		double maxSize = pixPerEdge()/3.5;
		System.out.println((int)maxSize);

		//For each Edge
		for(int count = 0; count < edges.size(); count++){

			//Add a new Line
			lines.add(new Line());
			//Set that new Line's blobID to the edges' ID
			lines.get(lines.size()-1).blobID = edges.get(count).blobID;
			//Set the first Pixel's isLine to TRUE
			(edges.get(count).pixels.get(0)).isLine = true;
			//Add the first Pixels of the Edge to the Line
			lines.get(lines.size()-1).pixels.add(edges.get(count).pixels.get(0));
			//For every other Pixel on the Edge
			for(int count1 = 1; count1 < edges.get(count).pixels.size()-1; count1++){

				//Add the next Pixel, and set it's isLine value to TRUE
				lines.get(lines.size()-1).pixels.add(edges.get(count).pixels.get(count1));
				edges.get(count).pixels.get(count1).isLine = true;
				//If the Line has at least 5 elements
				if(lines.get(lines.size()-1).pixels.size() > 5){
					//Compute the regression of that line
					lines.get(lines.size()-1).regression();
				}

				//If the regression's r value is below the tolerance and there are
				//only 5 Pixels in the Line (counter is 0), add one the counter
				//(give it another try to improve the r value)
				if(lines.get(lines.size()-1).r < tolerance && counter == 0){

					counter++;
				}

				//If after another try or few the r value is below the tolerance, 
				//terminate the Line by removing the 'defecting' Pixel and computing
				//the regression again, then adding it to the list of lines for the edge.
				//Finally, create a new line in a similar manner to before.
				else if(lines.get(lines.size()-1).r < tolerance && counter != 0){

					lines.get(lines.size()-1).pixels.get(lines.get(lines.size()-1).pixels.size()-1).isLine = false;
					lines.get(lines.size()-1).pixels.remove(lines.get(lines.size()-1).pixels.size()-1);
					lines.get(lines.size()-1).regression();
					edges.get(count).lines.add(lines.get(lines.size()-1));
					lines.add(new Line());
					lines.get(lines.size()-1).blobID = edges.get(count).blobID;
					lines.get(lines.size()-1).pixels.add(edges.get(count).pixels.get(count1));
					counter = 0;
				}

				//If at any time the length of the Line exceeds the maxSize of a Line,
				//terminate the line and make a new one.
				if(lines.get(lines.size()-1).pixels.size() == (int)maxSize){

					lines.get(lines.size()-1).pixels.get(lines.get(lines.size()-1).pixels.size()-1).isLine = false;
					lines.get(lines.size()-1).pixels.remove(lines.get(lines.size()-1).pixels.size()-1);
					lines.get(lines.size()-1).regression();
					edges.get(count).lines.add(lines.get(lines.size()-1));
					lines.add(new Line());
					lines.get(lines.size()-1).blobID = edges.get(count).blobID;
					lines.get(lines.size()-1).pixels.add(edges.get(count).pixels.get(count1));
				}
			}

			//Add the last Line to the edges lines list
			edges.get(count).lines.add(lines.get(lines.size()-1));
		}

	}

	/**
	 * This method reduces the number of Lines in the image by selecting Lines that are
	 * only larger than the minSize. Additionally, this method removes Lines that are
	 * erroneous. This includes lines that are next to the edge of the image with really
	 * large slopes or 0 slope due to the white border around the image.
	 * 
	 * @param minSize The minimum size (number of Pixels) for a Line
	 */

	public void reduceLines(int minSize){

		int counter = 0;
		//For every Edge
		for(int count = 0; count < edges.size(); count++){
			//For each Line in that Edge
			for(int count1 = 0; count1 < edges.get(count).lines.size(); count1++){
				//If the Line is smaller than the minimum size or is an artifact
				//of the white border around the edge
				if(edges.get(count).lines.get(count1).pixels.size() < minSize || 
						(edges.get(count).lines.get(count1).pixels.get(0).x == 6 && edges.get(count).lines.get(count1).slope == 10000) ||
						(edges.get(count).lines.get(count1).pixels.get(0).y == 6 && edges.get(count).lines.get(count1).slope == 0) ||
						(edges.get(count).lines.get(count1).pixels.get(0).x == 1532 && edges.get(count).lines.get(count1).slope == 10000) ||
						(edges.get(count).lines.get(count1).pixels.get(0).y == 1532 && edges.get(count).lines.get(count1).slope == 0)){
					//For each Pixel in that Line, change its isLine to false
					for(int count2 = 0; count2 < edges.get(count).lines.get(count1).pixels.size(); count2++){
						edges.get(count).lines.get(count1).pixels.get(count2).isLine = false;
					}
					//Remove that Line from the Edge
					edges.get(count).lines.remove(count1);
					//Remove the Line
					lines.remove(counter);
					counter--;
					count1--;
				}
				counter++;
			}
		}
	}

	/**
	 * This method simply draws the Lines and the Edges in green and red, respectively.
	 * 
	 * @param location The output image location.
	 * @param name The output image name.
	 */

	public void drawLines(File location, String name){

		BufferedImage newImage = new BufferedImage(edger.scaledPixels.length, edger.scaledPixels.length, BufferedImage.TYPE_INT_RGB);

		for(int count = 0; count < edges.size(); count++){
			for(int count1 = 0; count1 < edges.get(count).pixels.size(); count1++){

				if(edges.get(count).pixels.get(count1).isLine){
					newImage.setRGB(edges.get(count).pixels.get(count1).x, edges.get(count).pixels.get(count1).y, Color.GREEN.getRGB());
				}
				else{
					newImage.setRGB(edges.get(count).pixels.get(count1).x, edges.get(count).pixels.get(count1).y, Color.RED.getRGB());
				}
			}
		}
		try {
			File rendered = new File(location.getAbsolutePath(), name + "Lines.png");
			ImageIO.write(newImage, "png", rendered);
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	public void createGrains(){

		LinkedList<Integer> spread = new LinkedList<Integer>();
		int counter = 0;
		ScaledBlob currBlob;
		ScaledBlob tempBlob;
		Line tempLine;

		for(int count = 0; count < lines.size(); count++){

			currBlob = findBlob(lines.get(count).blobID);

			if(!currBlob.inGrain){
				spread.addAll(checkAroundLine(lines.get(count)));
				if(!spread.isEmpty()){
					grains.add(new Grain(counter));
					grains.get(grains.size()-1).scaledBlobs.add(currBlob);
					assignGrainID(currBlob, counter);
					currBlob.inGrain = true;
					counter++;
				}
			}
			while(!spread.isEmpty()){

				tempLine = lines.get(spread.poll());
				tempBlob = findBlob(tempLine.blobID);
				if(!tempBlob.inGrain){
					grains.get(grains.size()-1).scaledBlobs.add(tempBlob);
					assignGrainID(tempBlob, counter-1);
					tempBlob.inGrain = true;
				}
				spread.addAll(checkAroundLine(tempLine));
			}
		}
	}

	public void assignGrainID(ScaledBlob b, int desired){

		for(int count = 0; count < b.pixels.size(); count++){

			b.pixels.get(count).grainID = desired;
		}

	}

	public ScaledBlob findBlob(int id){

		if(idKey.contains(id)){
			return scaledBlobs.get(idKey.indexOf(id));
		}

		return null;
	}

	public ArrayList<Integer> checkAroundLine(Line line){

		ArrayList<Integer> indeces = new ArrayList<Integer>();

		for(int count = 0; count < lines.size(); count++){

			if((Math.abs(line.slope-lines.get(count).slope) < .75) && 
					(line.blobID != lines.get(count).blobID) && 
					!findBlob(lines.get(count).blobID).inGrain && 
					(line != lines.get(count))){

				for(int count1 = 0; count1 < lines.get(count).pixels.size(); count1++){

					if(distance(line.pixels.get(line.pixels.size()/2), lines.get(count).pixels.get(count1)) < 50){

						indeces.add(count);
						count1 = lines.get(count).pixels.size();
					}
				}
			}
		}

		return indeces;
	}

	public double distance(Pixel p1, Pixel p2){

		double distance = Math.sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
		return distance;
	}

	public void reduceGrains(){

		for(int count = 0; count < grains.size(); count++){

			if(grains.get(count).scaledBlobs.size() < 3){
				grains.remove(count);
				count--;
			}
		}
	}

	public boolean isAGrainID(int id){

		for(int count = 0; count < grains.size(); count++){

			if(grains.get(count).grainID == id){
				return true;
			}
		}

		return false;
	}

	public void drawGrains(File location, String name){

		Color[] colors = new Color[10];
		colors[0] = Color.BLUE;
		colors[1] = Color.GREEN;
		colors[2] = Color.ORANGE;
		colors[3] = Color.YELLOW;
		colors[4] = Color.CYAN;
		colors[5] = Color.DARK_GRAY;
		colors[6] = Color.GRAY;
		colors[7] = Color.MAGENTA;
		colors[8] = Color.PINK;
		colors[9] = Color.WHITE;

		BufferedImage newImage = new BufferedImage(edger.scaledPixels.length, edger.scaledPixels.length, BufferedImage.TYPE_INT_RGB);

		for(int count = 0; count < edger.scaledPixels.length; count++){
			for(int count1 = 0; count1 < edger.scaledPixels.length; count1++){

				if(edger.scaledPixels[count1][count].isEdge){
					newImage.setRGB(count1, count, Color.RED.getRGB());
				}
				else{
					if(!isAGrainID(edger.scaledPixels[count1][count].grainID)){
						newImage.setRGB(count1, count, Color.BLACK.getRGB());
					}
					else{
						newImage.setRGB(count1, count, colors[findGrain(edger.scaledPixels[count1][count].grainID)%10].getRGB());
					}
				}
			}
		}
		try {
			File rendered = new File(location.getAbsolutePath(), name + "Grains.png");
			ImageIO.write(newImage, "png", rendered);
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	public void drawPerpendicularAngleGrains(File location, String name){

		Color[] colors = new Color[10];
		colors[0] = Color.BLUE;
		colors[1] = Color.GREEN;
		colors[2] = Color.ORANGE;
		colors[3] = Color.YELLOW;
		colors[4] = Color.CYAN;
		colors[5] = Color.DARK_GRAY;
		colors[6] = Color.GRAY;
		colors[7] = Color.MAGENTA;
		colors[8] = Color.PINK;
		colors[9] = Color.WHITE;

		BufferedImage newImage = new BufferedImage(edger.scaledPixels.length, edger.scaledPixels.length, BufferedImage.TYPE_INT_RGB);

		for(int count = 0; count < edger.scaledPixels.length; count++){
			for(int count1 = 0; count1 < edger.scaledPixels.length; count1++){
				if(edger.scaledPixels[count1][count].grainID == -1){
					newImage.setRGB(count1, count, Color.BLACK.getRGB());
				}
				else
					if(edger.scaledPixels[count1][count].isPerpendicular){
						newImage.setRGB(count1, count, colors[edger.scaledPixels[count1][count].grainID%10].getRGB());
					}
					else{
						newImage.setRGB(count1, count, Color.BLACK.getRGB());
					}

			}
		}

		try {
			File rendered = new File(location.getAbsolutePath(), name + "Grains.png");
			ImageIO.write(newImage, "png", rendered);
		} catch (IOException e) {
			System.out.println(e);
		}

	}

	public int findGrain(int id){

		for(int count = 0; count < grains.size(); count++){

			if(grains.get(count).grainID == id){

				return count;
			}
		}

		return -1;
	}

	/**
	 * This method finds the double centers of the Blobs in the scaledBlobs ArrayList.
	 * It also adds the integer version of the centers to the centers array.
	 * 
	 */

	public int numberOfPerpendicular(){

		int counter = 0;

		for(int index = 0; index < scaledBlobs.size(); index++){
			if(scaledBlobs.get(index).pixels.get(0).isPerpendicular){
				counter++;
			}

		}

		return counter;

	}

	public void getCenters(){

		if(scaledBlobs == null){
			edger.scaleUp(blobArray);
			scaledBlobs = edger.scaledBlobArray;
		}

		for(int count = 0; count < scaledBlobs.size(); count++){
			int x = 0;
			int y = 0;
			if(scaledBlobs.get(count).pixels.get(0).isPerpendicular){
				for(Pixel curr: scaledBlobs.get(count).pixels){
					x = x + curr.x;
					y = y + curr.y;
				}
				Integer[] i = new Integer[2];
				i[0] = x/scaledBlobs.get(count).pixels.size();
				i[1] = y/scaledBlobs.get(count).pixels.size();
				centers.add(i);
				scaledBlobs.get(count).center[0] = x/(double)scaledBlobs.get(count).pixels.size();
				scaledBlobs.get(count).center[1] = y/(double)scaledBlobs.get(count).pixels.size();
			}
		}
	}

	/**
	 * This method gets the closestBlobs of every Blob in the ScaledBlob ArrayList.
	 * The closest Blobs are determined by center to center distances between Blobs
	 * being less than a maximum value, which is the input parameter.
	 * 
	 * @param maxDistance The maximum radial distance between two Blob centers.
	 */

	public void determineClosest(double maxDistance){

		double x1, x2, y1, y2, distance;
		double slope = 0;

		for(int index = 0; index < scaledBlobs.size(); index++){

			if(scaledBlobs.get(index).pixels.get(0).isPerpendicular){
				for(int count = 0; count < scaledBlobs.size(); count ++){
					if(scaledBlobs.get(count).pixels.get(0).isPerpendicular && scaledBlobs.get(count) != scaledBlobs.get(index)){

						x1 = scaledBlobs.get(index).center[0];
						x2 = scaledBlobs.get(count).center[0];
						y1 = scaledBlobs.get(index).center[1];
						y2 = scaledBlobs.get(count).center[1];
						distance = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
						if(distance <= maxDistance){
							//Add the prospective Blob to the closestBlobs array of the current Blob
							scaledBlobs.get(index).closestBlobs.add(scaledBlobs.get(count));
							slope = (y1-y2)/(x2-x1);

							if(x1-x2 == 0){
								slope = 1000000;
							}
							scaledBlobs.get(index).slopes.add(slope);
						}
					}
				}
			}

		}

	}

	/**
	 * This method determines the grains of the perpendicular cylinders using an
	 * angle based method. This method compares the slopes of nearest neighbor 
	 * perpendicular blobs to a series of hexagonally aligned angles to try to match
	 * similar angles to a grain
	 * 
	 * @param tolerance This is the tolerance to classify an angle as a particular grain
	 * @param maxDistance This is the maximum distance alloted for a close blob
	 */

	public void getAngleGrains(double tolerance, double maxDistance){

		this.getCenters();
		this.determineClosest(maxDistance);

		int numPerp = numberOfPerpendicular();

		double currAngle;
		ScaledBlob currBlob;
		int grainIndex = 0;
		int counter = 0;

		for(int index = 0; index<scaledBlobs.size(); index++){
			//Set the current Blob to currBlob
			currBlob = scaledBlobs.get(index);
			if(currBlob.pixels.get(0).isPerpendicular){
				counter = counter + 1;
			}
			//If it is perpendicular and not in a grain
			if(currBlob.pixels.get(0).isPerpendicular && !currBlob.inGrain){
				//Make a new grain for this blob
				currBlob.grainID = grainIndex;
				currBlob.inGrain = true;
				//Increment the grain index
				grainIndex++;
				if(grainIndex == 1){
					System.out.println(currBlob.closestBlobs.size());

				}
				//If the currBlob has any closest blobs
				if(currBlob.closestBlobs.size() != 0){
					//Find a current angle between the Blob and its first closest one
					currAngle = Math.atan(currBlob.slopes.get(0));
					//Iterate through for that grain angle
					scanAngle(currBlob,0,currAngle,tolerance);
				}

			}

			System.out.println("Here I am, the current grain index is " + grainIndex);
			System.out.println("We are through " + counter + " of " + numPerp + " blobs");
		}

		System.out.println("Coloring the grains...");
		this.colorGrainPixels();

	}

	/**
	 * This method simply designates the pixels of a ScaledBlob with a grainID that is
	 * equal to that of the ScaledBlob's grainID.
	 * 
	 */

	public void colorGrainPixels(){


		for(int index = 0; index<scaledBlobs.size(); index++){
			if(scaledBlobs.get(index).inGrain){
				for(int count = 0; count < scaledBlobs.get(index).pixels.size(); count++){
					scaledBlobs.get(index).pixels.get(count).grainID = 
							scaledBlobs.get(index).grainID;
				}
			}
		}

	}

	/**
	 * This method recursively solves for grains. It first has base cases, which terminate the
	 * recursion. Next, the method checks to see if any closest centers lie on a hexagonal grain.
	 * If they do, recursively check that next close Blob on the same grain. If none do, then increment
	 * the counter and continue recursion.
	 * 
	 * @param currBlob The current blob
	 * @param current The index of the current closest blob to the currBlob
	 * @param angle The angle to be checked
	 * @param tolerance The tolerance for the angle checking
	 */

	public void scanAngle(Blob currBlob, int current, double angle, double tolerance){

		//Base case: if the current is the size of the array, stop
		if(current >= currBlob.closestBlobs.size()){
			return;
		}

		//Base case: if the current closest blob is already in a grain, stop
		if(currBlob.closestBlobs.get(current).inGrain){
			current = current +1;
			scanAngle(currBlob,current, angle,tolerance);
			return;
		}
		
		//System.out.print("The current blob is: ");
		//System.out.println(currBlob);
		
		

		/*if(currBlob == scaledBlobs.get(1)){
			System.out.println("For the first perpendicular blob, the angle is " + angle);
			System.out.print("Current blob: ");
			System.out.println(currBlob);
			System.out.println("Closest blobs IDs and centers: ");
			for(int index = 0; index<currBlob.closestBlobs.size(); index++){

				System.out.println(currBlob.closestBlobs.get(index));
			}
			System.out.println("Current: " + current);
		}
		if(currBlob == scaledBlobs.get(1).closestBlobs.get(1)){
			System.out.println("For the second perpendicular blob, the angle is " + angle);
		}
		if(currBlob == scaledBlobs.get(1).closestBlobs.get(0)){
			System.out.println("For the second perpendicular blob, the angle is " + angle);
		}*/
		
		//If any of these closest blobs are within the hex pattern grain of the
		//angle, add them to the grain and continue searching
		
		//If the angle is in the top third of the hex
		if(angle >= Math.PI/6 && angle <= Math.PI/2){
			//If the angle between the two is commensurate within tolerance
			//The PI/2 is added to make the tolerance check simpler
			if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= angle+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= angle-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;

				scanAngle(currBlob.closestBlobs.get(current), 0, angle, tolerance);
				return;
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle-Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle-Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;
				
				scanAngle(currBlob.closestBlobs.get(current), 0, (angle-Math.PI/3), tolerance);
				return;
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle-2*Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle-2*Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;

				scanAngle(currBlob.closestBlobs.get(current), 0, (angle-2*Math.PI/3), tolerance);
				return;
			}
		}
		//If the angle is in the middle third of the hex
		else if(angle >= -Math.PI/6 && angle <= Math.PI/6){
			if(currBlob == scaledBlobs.get(1)||currBlob == scaledBlobs.get(1).closestBlobs.get(0)){
				System.out.println("In the middle third");
			}
			//If the angle between the two is commensurate within tolerance
			//The PI/2 is added to make the tolerance check simpler
			if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle+Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle+Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;
//				if(currBlob == scaledBlobs.get(1)){
//					System.out.println("For the first one, found a grain at " + (angle+Math.PI/3));
//					System.out.println("Current: " + current);
//				}
//				if(currBlob == scaledBlobs.get(1).closestBlobs.get(0)){
//					System.out.println("For the second one, found a grain at " + (angle+Math.PI/3));
//				}
				
				scanAngle(currBlob.closestBlobs.get(current), 0, (angle+Math.PI/3), tolerance);
				return;
				
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= angle+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= angle-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;
//				if(currBlob == scaledBlobs.get(1)){
//					System.out.println("For the first one, found a grain at " + (angle));
//					System.out.println("Current: " + current);
//					System.out.print("Passing this blob: ");
//					System.out.println(currBlob.closestBlobs.get(current));
//				}
//				if(currBlob == scaledBlobs.get(1).closestBlobs.get(0)){
//					System.out.println("For the second one, found a grain at " + (angle));
//				}
				
				scanAngle(currBlob.closestBlobs.get(current), 0, angle, tolerance);
				return;
				
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle-Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle-Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;
//				if(currBlob == scaledBlobs.get(1)){
//					System.out.println("For the first one, found a grain at " + (angle-Math.PI/3));
//					System.out.println("Current: " + current);
//				}
//				if(currBlob == scaledBlobs.get(1).closestBlobs.get(0)){
//					System.out.println("For the second one, found a grain at " + (angle-Math.PI/3));
//				}

				scanAngle(currBlob.closestBlobs.get(current), 0, (angle-Math.PI/3), tolerance);
				return;
				
			}
		}
		//If the angle is in the bottom third of the hex
		else{
			//If the angle between the two is commensurate within tolerance
			//The PI/2 is added to make the tolerance check simpler
			if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle+2*Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle+2*Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;

				scanAngle(currBlob.closestBlobs.get(current), 0, (angle+2*Math.PI/3), tolerance);
				return;
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= (angle+Math.PI/3)+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= (angle+Math.PI/3)-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;

				scanAngle(currBlob.closestBlobs.get(current), 0, (angle+Math.PI/3), tolerance);
				return;
			}
			else if((Math.atan(currBlob.slopes.get(current))+Math.PI/2 <= angle+tolerance+Math.PI/2)&&
					(Math.atan(currBlob.slopes.get(current))+Math.PI/2 >= angle-tolerance+Math.PI/2)){
				//Add it to the grain, and recursively look at that new Blob at the
				//same angle
				currBlob.closestBlobs.get(current).grainID = currBlob.grainID;
				currBlob.closestBlobs.get(current).inGrain = true;

				scanAngle(currBlob.closestBlobs.get(current), 0, angle, tolerance);
				return;
			}

		}

		//If it doesn't satisfy any of these conditions, increase the counter
		//and try again
		current = current + 1;
		scanAngle(currBlob, current, angle, tolerance);

	}

	public void getVoronoiEdges(){

		getCenters();
		ArrayList<Double> distribution = new ArrayList<Double>();
		double[] xValues = new double[centers.size()];
		double[] yValues = new double[centers.size()];
		double distance, x1, y1, x2, y2;
		for(int count = 0; count < centers.size(); count++){
			for(int count1 = 0; count1 < centers.size(); count1++){
				x1 = centers.get(count)[0];
				x2 = centers.get(count1)[0];
				y1 = centers.get(count)[1];
				y2 = centers.get(count1)[1];
				distance = Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
				distribution.add(distance);
				xValues[count] = x1;
				yValues[count] = y1;
			}
		}

		double min = distribution.get(0);

		for(int count = 0; count < distribution.size(); count++){
			if(distribution.get(count) < min){
				min = distribution.get(count);
			}
		}

		Voronoi v = new Voronoi(min);
		double minX = 0;
		double minY = 0;
		double maxX = 512*3-1;
		double maxY = 512*3-1;
		voronoiEdges = v.generateVoronoi(xValues, yValues, minX, maxX, minY, maxY);

		voronoiLines = new ArrayList<Integer[]>();

		for(GraphEdge g: voronoiEdges){
			voronoiLines.addAll(drawLine((int)g.x1, (int)g.y1, (int)g.x2, (int)g.y2));
		}

	}

	public ArrayList<Integer[]> drawLine(int x1, int y1, int x2, int y2){

		ArrayList<Integer[]> line = new ArrayList<Integer[]>();
		Integer[] vals;
		vals = new Integer[2];
		vals[0] = x1;
		vals[1] = y1;
		line.add(vals);
		vals = new Integer[2];
		vals[0] = x2;
		vals[1] = y2;
		line.add(vals);

		if((x2-x1) == 0){
			for(int count = 0; count < Math.abs(y2-y1); count++){
				vals = new Integer[2];
				vals[0] = x1;
				if(y2 > y1){
					vals[1] = y1+count;
				}
				else if(y1 > y2){
					vals[1] = y2+count;
				}
				else{
					return line;
				}
				line.add(vals);
			}
		}
		else{
			double slope = (double)(y2-y1)/(x2-x1);
			int counter = 1;

			if(slope > 1 || slope < -1){
				while(y1 + counter < y2){
					vals = new Integer[2];
					vals[0] = x1 + (int)(counter/slope);
					vals[1] = y1 + counter;
					line.add(vals);
					counter++;
				}
			}
			else if(slope == 1 || slope == -1){
				if(x2 > x1){
					while(x1 + counter < x2){
						vals = new Integer[2];
						vals[0] = x1 + counter;
						vals[1] = y1 + counter*(int)slope;
						line.add(vals);
						counter++;
					}
				}
				else{
					while(x2 + counter < x1){
						vals = new Integer[2];
						vals[0] = x2 + counter;
						vals[1] = y2 + counter*(int)slope;
						line.add(vals);
						counter++;
					}
				}
			}
			else{
				while(x1 + counter < x2){
					vals = new Integer[2];
					vals[0] = x1 + counter;
					vals[1] = y1 + (int)(slope*counter);
					line.add(vals);
					counter++;
				}
			}
		}

		return line;
	}

	public void generateFinalScaledPicture(String name, File location, boolean perp, boolean par){

		if(edger.scaledPixels == null){
			edger.scaleUp(blobArray);
		}

		BufferedImage newImage = new BufferedImage(this.edger.scaledPixels.length, this.edger.scaledPixels.length, BufferedImage.TYPE_INT_RGB);

		getVoronoiEdges();

		generatePolygons();

		for(VoronoiPolygon p: polygons){
			ArrayList<Integer[]> inside = drawPolygon(p);
			if(p.numSides == 6){
				for(Integer[] i: inside){
					newImage.setRGB(i[0], i[1], Color.GREEN.getRGB());
				}
			}
			else{
				for(Integer[] i: inside){
					newImage.setRGB(i[0], i[1], Color.CYAN.getRGB());
				}
			}
		}

		for(Integer[] i: voronoiLines){
			newImage.setRGB(i[0], i[1], Color.RED.getRGB());
		}

		for(int count = 0; count < this.edger.scaledPixels.length; count = count + 1){
			for(int count1 = 0; count1 < this.edger.scaledPixels.length; count1 = count1 + 1){
				if(this.edger.scaledPixels[count1][count].isPerpendicular == true && perp){
					newImage.setRGB(count1, count, Color.YELLOW.getRGB());
				}
				else if(this.edger.scaledPixels[count1][count].isParallel == true && par){
					newImage.setRGB(count1, count, Color.BLUE.getRGB());
				}
				/*else{
					newImage.setRGB(count1, count, Color.BLACK.getRGB());
				}*/
			}
		}

		/*for(Polygon p: polygons){
		for(GraphEdge g: p.edges){
			ArrayList<Integer[]> i = drawLine((int)g.x1, (int)g.y1, (int)g.x2, (int)g.y2);
			for(Integer[] is: i){
				newImage.setRGB(is[0], is[1], Color.GREEN.getRGB());
			}
		}
		}*/

		try {
			File rendered = new File(location.getAbsolutePath(), name + "Scaled.png");
			ImageIO.write(newImage, "png", rendered);
		} catch (IOException e) {
			System.out.println(e);
		}
	}

	public void generatePolygons(){

		HashSet<Integer> usedSites;

		for(GraphEdge g1: voronoiEdges){

			usedSites = new HashSet<Integer>();
			if(!usedSites.contains(g1.site1)){
				usedSites.add(g1.site1);
				VoronoiPolygon poly1 = new VoronoiPolygon(g1);
				for(GraphEdge g2: voronoiEdges){
					if(g2 != g1 && (g2.site1 == g1.site1 || g2.site2 == g1.site1)){
						poly1.edges.add(g2);
						poly1.numSides++;
						poly1.addPoint((int)g2.x1, (int)g2.y1);
						poly1.addPoint((int)g2.x2, (int)g2.y2);
					}
				}
				polygons.add(poly1);
			}
			if(!usedSites.contains(g1.site2)){
				usedSites.add(g1.site1);
				VoronoiPolygon poly2 = new VoronoiPolygon(g1);
				for(GraphEdge g2: voronoiEdges){
					if(g2 != g1 && (g2.site1 == g1.site2 || g2.site2 == g1.site2)){
						poly2.edges.add(g2);
						poly2.numSides++;
						poly2.addPoint((int)g2.x1, (int)g2.y1);
						poly2.addPoint((int)g2.x2, (int)g2.y2);
					}
				}
				polygons.add(poly2);
			}
		}
	}

	public ArrayList<Integer[]> drawPolygon(VoronoiPolygon poly){

		Integer[] vals;
		ArrayList<Integer[]> inside = new ArrayList<Integer[]>();

		for(int x = 0; x < 512*3; x++){
			for(int y = 0; y < 512*3; y++){
				if(poly.contains(x, y)){
					vals = new Integer[2];
					vals[0] = x;
					vals[1] = y;
					inside.add(vals);
				}
			}
		}

		return inside;

	}

}
